/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#include "carina/debug-gui.hh"
#include "carina/common/exception.hh"
#include "carina/math.hh"
#include "carina/common/global.hh"

// Ye Olde C
#include <cassert>

// Fonts
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_OUTLINE_H
#include FT_TRIGONOMETRY_H

// STL
#include <sstream>
#include <iomanip>

namespace Carina
{
namespace GUI
{
CharRenderer::CharRenderer(ResourceManager& res_mgr)
    :   m_Renderer(res_mgr.getRenderer())
{
    m_Prog = res_mgr.getShaderProgram("text_rendering");

    m_Renderer->useShaderProgram(m_Prog);
    u_TransformMatrix = m_Prog->getVariable("TransformMatrix");
    u_Color = m_Prog->getVariable("Color");
    u_Texture = m_Prog->getTexture("TextTexture");

    u_Color->set(Vector3(1.0f, 1.0f, 1.0f));

    InputElementArray elems(2);
    elems[0].set("POSITION", VT_RG32F, 0);
    elems[1].set("TEXCOORD", VT_RG32F, sizeof(Vector2));
    m_Layout = m_Renderer->createInputLayout(m_Prog, elems);

    m_State = m_Renderer->createStateObject(MiscStateDescription(CE_FILL_SOLID, CE_CULL_NONE, 0.0f, 0.0f, false, false, false, false));
    m_BlendState = m_Renderer->createBlendStateObject(BlendStateDescription(true, CE_BLEND_SRC_ALPHA, CE_BLEND_INV_SRC_ALPHA,
																			CE_BLEND_OP_ADD, CE_BLEND_ZERO, CE_BLEND_ZERO,
																			CE_BLEND_OP_ADD));
    m_DepthStencilState = m_Renderer->createDepthStencilStateObject(DepthStencilStateDescription(false, false));
}

void CharRenderer::setup(const FontPtr& font, ScopedState& state, ScopedBlendState& blend_state, ScopedDepthStencilState& depth_stencil)
{
    m_Font = font;
    state.setState(m_State);
    blend_state.setState(m_BlendState);
    depth_stencil.setState(m_DepthStencilState);
    m_Renderer->useShaderProgram(m_Prog);
    m_Renderer->setVideoBuffer(font->getVBO(), 2*sizeof(Vector2), 0);
    m_Renderer->setInputLayout(m_Layout);
    u_Texture->set(font->getTexture());
}

void CharRenderer::renderChar(unsigned ch, Matrix4& mat)
{
    assert(ch < 128);

    m_Renderer->draw(CE_TRIANGLE_STRIP, ch*4, 4);
    mat.translateX(m_Font->getCharAdvance(ch));
}

#undef max

Font::Font(const RendererPtr& renderer, const string& filename)
    :   m_FontHeight(16)
{
    auto ftlib = CREATE_SCOPED(FT_Library, FT_Done_FreeType);
    if(FT_Init_FreeType(&ftlib))
        THROW_EXCEPTION("Unknown reason");

    auto ftface = CREATE_SCOPED(FT_Face, FT_Done_Face);
    if(FT_New_Face(ftlib, filename.c_str(), 0, &ftface))
        THROW_EXCEPTION("There is probably a problem with your font file");

    FT_Set_Char_Size(ftface, m_FontHeight * 64, m_FontHeight * 64, 96, 96);

    int maxdim = pow_of_2(std::max(ftface->bbox.xMax - ftface->bbox.xMin,
                                   ftface->bbox.yMax - ftface->bbox.yMin) / 64);

    int dimsize = maxdim * 16;
    DynamicArray<unsigned char> texture(dimsize * dimsize);

    memset(texture.get(), 0, texture.size());
    struct CharCoords
    {
        Vector2 pos;
        Vector2 texcoord;
    } coords[128][4];

    unsigned char *ptr1;
    DynamicArray<unsigned char>::iterator tex_iter;
    float xtex_s, xtex_e, ytex_s, ytex_e;
    int p, q, k;
    for(int i = 0; i < 128; ++i)
    {
        if(FT_Load_Glyph(ftface, FT_Get_Char_Index(ftface, i), FT_LOAD_DEFAULT))
            THROW_EXCEPTION("Unknown reason");

        auto glyph = CREATE_SCOPED(FT_Glyph, FT_Done_Glyph);
        if(FT_Get_Glyph(ftface->glyph, &glyph))
            THROW_EXCEPTION("Unknown reason");

        FT_Glyph_To_Bitmap(&glyph, ft_render_mode_normal, 0, 1);
        FT_BitmapGlyph bmglyph = (FT_BitmapGlyph)glyph.get();
        FT_Bitmap& bitmap=bmglyph->bitmap;

        p = i / 16;
        q = i % 16;
        for(k = 0, ptr1 = bitmap.buffer, tex_iter = texture.begin() + maxdim*(p*dimsize + q);
            k < bitmap.rows;
            ++k, ptr1 += bitmap.width, tex_iter += dimsize)
        {
            std::copy(ptr1, ptr1 + bitmap.width, tex_iter);
        }

        xtex_s = (float)(q*maxdim)/dimsize;
        xtex_e = xtex_s + (float)bitmap.width/dimsize;
        ytex_s = (float)(p*maxdim)/dimsize;
        ytex_e = ytex_s + (float)bitmap.rows/dimsize;

        coords[i][0].pos.x() = 0.0f,                coords[i][0].pos.y() = 0.0f;
        coords[i][0].texcoord.x() = xtex_s,         coords[i][0].texcoord.y() = ytex_e;

        coords[i][1].pos.x() = 0.0f,                coords[i][1].pos.y() = (float)bitmap.rows;
        coords[i][1].texcoord.x() = xtex_s,         coords[i][1].texcoord.y() = ytex_s;

        coords[i][2].pos.x() = (float)bitmap.width, coords[i][2].pos.y() = 0.0f;
        coords[i][2].texcoord.x() = xtex_e,         coords[i][2].texcoord.y() = ytex_e;

        coords[i][3].pos.x() = (float)bitmap.width, coords[i][3].pos.y() = (float)bitmap.rows;
        coords[i][3].texcoord.x() = xtex_e,         coords[i][3].texcoord.y() = ytex_s;

        m_CharAdvance[i] = (float)ftface->glyph->advance.x / 64;
    }

    m_FontTexture = renderer->createTexture(TextureDescription(dimsize, dimsize, 1, VT_R8_UNORM), texture.get(), 0);
    m_VBO = renderer->createVideoBuffer(CE_VERTEX_BUFFER, sizeof(coords), coords, CE_STATIC_DRAW);
}

Font::~Font()
{
}



Label::Label()
    :   m_Font(GUI::System::getDefaultFont()),
        m_CharRenderer(GUI::System::getDefaultCharRenderer()),
        m_Color(1.0, 1.0f, 1.0f) {}

Label::Label(const string& str)
:   m_Font(GUI::System::getDefaultFont()),
    m_CharRenderer(GUI::System::getDefaultCharRenderer()),
    m_Text(str),
    m_Color(1.0, 1.0f, 1.0f) {}

void Label::render()
{
    size_t w, h;
    RendererPtr rend = m_CharRenderer->getRenderer();
    rend->getViewport(w, h);
    Matrix4 transform = rend->createOrthoMatrix(0, (float)w, 0, (float)h);

    Vector2 pos(m_Position.x() < 0.0f ? w + m_Position.x() : m_Position.x(),
                m_Position.y() < 0.0f ? h + m_Position.y() : m_Position.y());
    transform.translate(pos);

    ScopedDepthStencilState depth_stencil_state(rend);
    ScopedBlendState blend_state(rend);
    ScopedState state(rend);

    m_CharRenderer->setup(m_Font, state, blend_state, depth_stencil_state);
    m_CharRenderer->setColor(m_Color);

    for(unsigned i = 0; i < m_Text.size(); ++i)
    {
        m_CharRenderer->setTransformMatrix(transform);
        m_CharRenderer->renderChar(m_Text[i], transform);
    }
}

System::System(ResourceManager& rmgr)
    :   m_ResMgr(rmgr),
		m_Renderer(make_aligned_shared<CharRenderer>(rmgr)) 
{
    m_DefaultFont = this->getFont("arial.ttf");
}

FontPtr System::getFont(const string& str)
{
    GUI::System& sys = GUI::System::getSingleton();
    FontMap::iterator iter = sys.m_FontMap.find(str);
    if(iter == sys.m_FontMap.end())
    {
        FontPtr fp(make_aligned_shared<Font>(sys.m_ResMgr.getRenderer(), sys.m_ResMgr.getResource(str)));
        sys.m_FontMap.insert(std::make_pair(str, fp));
        return fp;
    }
    return iter->second;
}
}

FPSCounter::FPSCounter()
    :   next_frame(0),
        count_FPS(0),
        FPS(0)
{
    m_FPSText.setPosition(Vector2(10, 10));
}

FPSCounter::~FPSCounter()
{
}

void FPSCounter::update(nanosec time)
{
    if(time > next_frame)
    {
        next_frame = time + 1000000000;
        FPS = count_FPS;
        count_FPS = 0;
    }
    else
        ++count_FPS;

    std::stringstream ss;
    ss << "Frame rate: " << FPS << " FPS (" << std::setprecision(4) << (FPS ? 1000.0f / FPS : 0.0f) << " ms)";

    m_FPSText.setText(ss.str());
    m_FPSText.render();
}
}
